package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.controller.GoodsController
import com.zxy.supplier_system.server.controller.ProcurementReturnController
import com.zxy.supplier_system.server.controller.ProcurementReturnsItemController
import com.zxy.supplier_system.server.controller.ProcurementReturnsItemController.OrderItemInOrderSummary
import com.zxy.supplier_system.server.controller.ProcurementReturnsOrderController
import com.zxy.supplier_system.server.entity.*
import com.zxy.supplier_system.server.repository.*
import com.zxy.supplier_system.server.utils.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Slice
import org.springframework.data.jpa.domain.Specification
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.math.BigDecimal
import java.time.OffsetDateTime

@Service
class ProcurementReturnService(
    private val procurementSupplierRepository: ProcurementSupplierRepository,
    private val procurementReturnItemRepository: ProcurementReturnItemRepository,
    private val employeeRepository: EmployeeRepository,
    private val procurementReturnRepository: ProcurementReturnRepository,
    private val systemServiceService: SystemServiceService,
    private val goodsRepository: GoodsRepository,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
    private val procurementReceiptService: ProcurementReceiptService,
) {

    companion object {
        private val log by LoggerDelegate()
    }

    private val orderNumberGenerator = OrderNumberGenerator(initialCurrentSequence = {
        procurementReturnRepository.countToday()
    })

    @Transactional(readOnly = true)
    fun queryProcurementReturn(
        queryProcurementReturnRequest: ProcurementReturnController.QueryProcurementReturnRequest,
        pageable: Pageable
    ): Page<ProcurementReturnController.QueryProcurementReturnResponse> {

        return this.procurementReturnRepository.findAll(
            Specification.allOf(
                ProcurementReturnRepository.Specifications.supplierIn(queryProcurementReturnRequest.supplierIds),
                ProcurementReturnRepository.Specifications.endDateLessThanOrEqualTo(queryProcurementReturnRequest.endDate),
                ProcurementReturnRepository.Specifications.startDateGreaterThanOrEqualTo(queryProcurementReturnRequest.startDate),
                ProcurementReturnRepository.Specifications.createdByIn(queryProcurementReturnRequest.createdByIds),
//                ProcurementReturnRepository.Specifications.statusEqual(queryProcurementReturnRequest.status),
            ), pageable
        ).map { procurementReturn ->
            ProcurementReturnController.QueryProcurementReturnResponse(
                procurementReturn.id,
                procurementReturn.createdBy.let {
                    IntIdAndName(it.id!!, it.name)
                },
                procurementReturn.createdDateTime,
                procurementReturn.items.size,
                procurementReturn.supplier.let {
                    IntIdAndName(it.id, it.name)
                },
//                procurementReturn.status,
//                procurementReturn.receipt?.id,
                procurementReturn.number
            )
        }
    }

    @Transactional
    fun createProcurementReturn(supplierId: Int): Int {
        val procurementReturn = this.procurementReturnRepository.save(ProcurementReturn().apply {
            this.supplier = procurementSupplierRepository.findByIdOrNull(supplierId).orElse404()
            this.number = orderNumberGenerator.generate()
        })
        this.employeeOperateRecordService.createProcurementReturn(procurementReturn)
        return procurementReturn.id
    }

    @Transactional
    fun updateOrderGoods(
        id: Int,
        updateOrderGoodsRequest: ProcurementReturnController.UpdateOrderGoodsRequest
    ) {
        val procurementReturn = this.procurementReturnRepository.findByIdOrNull(id).orElse404()
        procurementReturn.items.clear()
        procurementReturn.items.addAll(
            updateOrderGoodsRequest.items.mapIndexed { index, it ->
                ProcurementReturnItem().apply {
                    this.procurementReturn = procurementReturn
                    this.amount = it.amount
                    this.goods =
                        goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(it.id, systemServiceService.getCurrentSystemService())
                            .orElse404()
                    this.sequence = index
                }
            }
        )
        this.procurementReturnRepository.save(procurementReturn)
    }

    @Transactional(readOnly = true)
    fun getProcurementReturnDetail(
        id: Int,
        includeItems: Boolean
    ): ProcurementReturnController.ProcurementReturnDetail {
        val procurementReturn = this.procurementReturnRepository.findByIdOrNull(id).orElse404()
        return ProcurementReturnController.ProcurementReturnDetail(
            procurementReturn.id,
            procurementReturn.createdDateTime,
            IntIdAndName(procurementReturn.createdBy.id!!, procurementReturn.createdBy.name),
            IntIdAndName(procurementReturn.supplier.id, procurementReturn.supplier.name),
            if (!includeItems) emptyList() else procurementReturn.items.map { item ->
                val goods = item.goods
                goods.let { it ->
                    ProcurementReturnController.ProcurementReturnItemResponse(
                        it.id,
                        it.name, it.barcode,
                        it.price,
                        it.amount,
                        it.createdDateTime,
                        it.lastModifiedDateTime,
                        it.brand?.toIntIdAndName(),
                        it.category?.toIntIdAndName(),
                        it.factory?.toIntIdAndName(),
                        item.amount,
                        it.boxSpecification,
                        item.subGoods?.let(GoodsController.GoodsDetail.SubGoodsResponse::fromSubGoods),
                        item.note,
                        item.price,
                    )
                }
            },
            procurementReturn.submittedDateTime,
//            procurementReturn.procurementReturnReceipt?.createdDateTime,
//            procurementReturn.receipt?.id,
        )
    }

    @Transactional
    fun addItem(
        orderId: Int,
        addProcurementReturnsItemRequest: ProcurementReturnsItemController.AddProcurementReturnsItemRequest
    ) {
        val procurementReturn = this.procurementReturnRepository.findByIdOrNull(orderId).orElse404()
        if (procurementReturn.items.any { it.goods.id == addProcurementReturnsItemRequest.goodsId && it.subGoods?.id == addProcurementReturnsItemRequest.subGoodsId })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        procurementReturn.items.add(ProcurementReturnItem().apply {
            this.procurementReturn = procurementReturn
            this.goods = goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(
                addProcurementReturnsItemRequest.goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
            this.subGoods =
                if (addProcurementReturnsItemRequest.subGoodsId != null)
                    this.goods.subGoodsList.find { it.id == addProcurementReturnsItemRequest.subGoodsId }.orElse404()
                else
                    null
            this.amount = addProcurementReturnsItemRequest.amount
            this.sequence = procurementReturn.items.nextSequence
            this.note = addProcurementReturnsItemRequest.note
            this.price = addProcurementReturnsItemRequest.price
        })
        this.procurementReturnRepository.save(procurementReturn)
        this.employeeOperateRecordService.addProcurementReturnItem(procurementReturn)
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int) {
        // TODO employee record
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            )
        if (procurementReturnItem != null) {
            this.procurementReturnItemRepository.delete(procurementReturnItem)
            this.employeeOperateRecordService.deleteProcurementReturnItem(procurementReturnItem)
        }
    }

    @Transactional
    fun deleteItem(orderId: Int, goodsId: Int, subGoodsId: Int) {
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndSubGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService(),
            )
        if (procurementReturnItem != null) {
            this.procurementReturnItemRepository.delete(procurementReturnItem)
            this.employeeOperateRecordService.deleteProcurementReturnItem(procurementReturnItem)
        }
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        updateItemAmountRequest: ProcurementReturnsItemController.UpdateItemAmountRequest
    ) {
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            ).orElse404()
        procurementReturnItem.amount = updateItemAmountRequest.amount
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemAmount(procurementReturnItem)
    }

    @Transactional
    fun updateItemAmount(
        goodsId: Int,
        orderId: Int,
        subGoodsId: Int,
        updateItemAmountRequest: ProcurementReturnsItemController.UpdateItemAmountRequest
    ) {
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndSubGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReturnItem.amount = updateItemAmountRequest.amount
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemAmount(procurementReturnItem)
    }

    fun getAllEmployees(): List<IntIdAndName> {
        return this.employeeRepository.findAll(
            EmployeeRepository.Specifications.haveProcurementReturn().and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    systemServiceService.getCurrentSystemService()
                )
            )
        ).map {
            IntIdAndName(it.id!!, it.name)
        }
    }

    @Transactional
    fun queryProcurementReturnInSupplier(
        supplierId: Int,
        pageable: Pageable
    ): Slice<ProcurementReturnsOrderController.QueryProcurementReturnInSupplierResponse> {
        return this.procurementReturnRepository.findAllBySupplier_Id(supplierId, pageable).map {
            ProcurementReturnsOrderController.QueryProcurementReturnInSupplierResponse(
                it.id,
                it.createdBy.toIntIdAndName(),
                it.createdDateTime,
                it.items.size,
                it.submittedDateTime,
            )
        }
    }

    fun queryOrderItemsInOrder(
        orderId: Int,
        pageable: Pageable
    ): Page<OrderItemInOrderSummary> {
        return this.procurementReturnItemRepository.findAllByProcurementReturn_IdOrderBySequenceAsc(orderId, pageable)
            .map {
                val goods = it.goods
                OrderItemInOrderSummary.fromGoodsAndAmountAndUnit(
                    goods,
                    it.amount,
                    null,
                    it.subGoods
                )
            }
    }

    fun exists(orderId: Int, goodsId: Int): Boolean {
        return this.procurementReturnItemRepository.existsByProcurementReturn_IdAndGoods_Id(orderId, goodsId)
    }

    fun exists(orderId: Int, barcode: String): Boolean {
        return this.procurementReturnItemRepository.existsByProcurementReturn_IdAndGoods_Barcode(orderId, barcode)
    }


    @Transactional
    fun addItem(orderId: Int, barcode: String): OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReturn = this.procurementReturnRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        if (procurementReturn.items.any { it.goods.barcode == barcode && it.subGoods == null })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        val goods =
            goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(barcode, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurementReturn, goods)
    }

    @Transactional
    fun submitProcurementReturn(id: Int) {
        val procurementReturn = this.procurementReturnRepository.findByIdAndSupplier_SystemService(
            id,
            systemServiceService.getCurrentSystemService()
        ).orElse404()
        if (procurementReturn.submittedDateTime == null) {
            procurementReturn.submittedDateTime = OffsetDateTime.now()
            this.procurementReturnRepository.save(procurementReturn)
//            this.employeeOperateRecordService.submitProcurementReturn(procurementReturn)
        } else {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST)
        }
    }

    @Transactional
    fun addItem(orderId: Int, goodsId: Int): OrderItemInOrderSummary {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReturn = this.procurementReturnRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        if (procurementReturn.items.any { it.goods.id == goodsId && it.subGoods == null })
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXISTED")
        val goods =
            goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(goodsId, currentSystemService)
                .orElse404()
        return orderItemInOrderSummary(procurementReturn, goods)
    }

    private fun orderItemInOrderSummary(
        procurementReturn: ProcurementReturn,
        goods: Goods,
        subGoods: SubGoods? = null
    ): OrderItemInOrderSummary {
        val latestItem =
            getLatestItemByProcurementReturnAndItemKey(procurementReturn.supplier, goods, subGoods)
        val amount = latestItem?.amount ?: 1
        val price =
            getLatestItemPurchaseByProcurementReturnAndItemKey(procurementReturn, goods, subGoods) ?: BigDecimal.ZERO
        procurementReturn.items.add(ProcurementReturnItem().apply {
            this.procurementReturn = procurementReturn
            this.goods =
                goods
            this.amount = amount
            this.sequence = procurementReturn.items.nextSequence
            this.price = price
            this.subGoods = subGoods
        })
        this.procurementReturnRepository.save(procurementReturn)
        this.employeeOperateRecordService.addProcurementReturnItem(procurementReturn)
        return OrderItemInOrderSummary.fromGoodsAndAmountAndUnit(
            goods,
            amount,
            null,
            subGoods,
            price
        )
    }


    //    @Transactional
//    fun receipt(id: Int): ProcurementReturnReceipt {
//        val procurementReturn = this.procurementReturnRepository.findByIdAndSupplier_SystemService(
//            id,
//            systemServiceService.getCurrentSystemService()
//        ).orElse404()
//        if (procurementReturn.submittedDateTime == null) {
//            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "订单未提交")
//        }
//        var procurementReturnReceipt = procurementReturnReceiptRepository.save(ProcurementReturnReceipt().apply {
//            this.supplier = procurementReturn.supplier
//            this.procurementReturn = procurementReturn
//            this.id = id
//        })
//        for (item in procurementReturn.items) {
//            val goods = item.goods
//            goods.amount -= item.amount
//            goodsRepository.save(goods)
//        }
//        procurementReturnReceipt = procurementReturnReceipt.apply {
//            this.items = procurementReturn.items.map { procurementReturnItem ->
//                ProcurementReturnReceiptItem().apply {
//                    this.procurementReturnReceipt = procurementReturnReceipt
//                    this.amount = procurementReturnItem.amount
//                    this.goods = procurementReturnItem.goods
//                    this.unit = procurementReturnItem.unit
//                }
//            }.toMutableList()
//        }
//        procurementReturnReceipt = this.procurementReturnReceiptRepository.save(procurementReturnReceipt)
//        this.employeeOperateRecordService.receipt(procurementReturn)
//        return procurementReturnReceipt
//    }
    @Transactional
    fun updateItemNote(
        goodsId: Int,
        orderId: Int,
        note: String,
        subGoodsId: Int
    ) {

        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndSubGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReturnItem.note = note
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemAmount(procurementReturnItem)

    }

    @Transactional
    fun updateItemNote(
        goodsId: Int,
        orderId: Int,
        note: String,
    ) {

        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReturnItem.note = note
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemAmount(procurementReturnItem)
    }

    @Transactional
    fun updateItemPrice(goodsId: Int, orderId: Int, price: BigDecimal) {
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReturnItem.price = price
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemPrice(procurementReturnItem)
    }


    @Transactional
    fun updateItemPrice(goodsId: Int, subGoodsId: Int, orderId: Int, price: BigDecimal) {
        val procurementReturnItem =
            this.procurementReturnItemRepository.findByProcurementReturn_IdAndGoods_IdAndSubGoods_IdAndProcurementReturn_Supplier_SystemService(
                orderId,
                goodsId,
                subGoodsId,
                systemServiceService.getCurrentSystemService()
            )
                .orElse404()
        procurementReturnItem.price = price
        this.procurementReturnItemRepository.save(procurementReturnItem)
        this.employeeOperateRecordService.updateProcurementReturnItemPrice(procurementReturnItem)
    }

    @Transactional
    fun batchAddItems(
        orderId: Int,
        itemKeys: List<ProcurementReturnsItemController.AddProcurementReturnItemKey>
    ): List<OrderItemInOrderSummary> {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val procurementReturn = this.procurementReturnRepository.findByIdAndSupplier_SystemService(
            orderId,
            currentSystemService
        ).orElse404()
        val goodsList =
            goodsRepository.findByIdInAndSystemServiceAndDisableIsFalse(itemKeys.map { it.goodsId }, currentSystemService)
                .orElse404()
        return itemKeys.map { key ->
            val goods = goodsList.find { it.id == key.goodsId }.orElse404()
            orderItemInOrderSummary(procurementReturn, goods, goods.subGoodsList.find { key.subGoodsId == it.id })
        }
    }

    fun getLatestItemByProcurementReturnAndItemKey(
        supplier: ProcurementSupplier,
        goods: Goods,
        subGoods: SubGoods?,
    ): ProcurementReturnItem? {
        return (
                if (subGoods != null)
                    this.procurementReturnItemRepository.findFirstByProcurementReturn_Supplier_IdAndGoods_IdAndSubGoods_IdOrderByProcurementReturn_CreatedDateTimeDesc(
                        supplier.id,
                        goods.id,
                        subGoods.id!!
                    )
                else
                    this.procurementReturnItemRepository.findFirstByProcurementReturn_Supplier_IdAndGoods_IdOrderByProcurementReturn_CreatedDateTimeDesc(
                        supplier.id,
                        goods.id,
                    )
                )
    }

    fun getLatestItemPurchaseByProcurementReturnAndItemKey(
        procurementReturn: ProcurementReturn,
        goods: Goods,
        subGoods: SubGoods? = null
    ): BigDecimal? {
        return getLatestItemByProcurementReturnAndItemKey(procurementReturn.supplier, goods, subGoods)?.price
            ?: this.procurementReceiptService.getLatestItemPurchaseByProcurementAndItemKey(
                procurementReturn.supplier,
                goods,
                subGoods
            )
    }
}